using System;
using System.Collections.Generic;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.VisualTree;

namespace Avalonia.Input
{
    public class PointerEventArgs : RoutedEventArgs
    {
        private readonly Visual? _rootVisual;
        private readonly Point _rootVisualPosition;
        private readonly PointerPointProperties _properties;
        private readonly Lazy<IReadOnlyList<RawPointerPoint>?>? _previousPoints;

        [Unstable("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")]
        public PointerEventArgs(RoutedEvent routedEvent,
            object? source,
            IPointer pointer,
            Visual? rootVisual, Point rootVisualPosition,
            ulong timestamp,
            PointerPointProperties properties,
            KeyModifiers modifiers)
           : base(routedEvent)
        {
            Source = source;
            _rootVisual = rootVisual;
            _rootVisualPosition = rootVisualPosition;
            _properties = properties;
            Pointer = pointer;
            Timestamp = timestamp;
            KeyModifiers = modifiers;
        }
        
        internal PointerEventArgs(RoutedEvent routedEvent,
            object? source,
            IPointer pointer,
            Visual? rootVisual, Point rootVisualPosition,
            ulong timestamp,
            PointerPointProperties properties,
            KeyModifiers modifiers,
            Lazy<IReadOnlyList<RawPointerPoint>?>? previousPoints)
#pragma warning disable CS0618
            : this(routedEvent, source, pointer, rootVisual, rootVisualPosition, timestamp, properties, modifiers)
#pragma warning restore CS0618
        {
            _previousPoints = previousPoints;
        }

        /// <summary>
        /// Gets specific pointer generated by input device.
        /// </summary>
        public IPointer Pointer { get; }

        /// <summary>
        /// Gets the time when the input occurred.
        /// </summary>
        public ulong Timestamp { get; }

        internal bool IsGestureRecognitionSkipped
        {
            get
            {
                return (Pointer as Pointer)?.IsGestureRecognitionSkipped ?? false;
            }
            private set
            {
                if (Pointer is Pointer pointer)
                    pointer.IsGestureRecognitionSkipped = true;
            }
        }

        /// <summary>
        /// Gets a value that indicates which key modifiers were active at the time that the pointer event was initiated.
        /// </summary>
        public KeyModifiers KeyModifiers { get; }

        private Point GetPosition(Point pt, Visual? relativeTo)
        {
            if (_rootVisual == null)
                return default;
            if (relativeTo == null)
                return pt;

            // If the visual the user passed in, is not connected to the same visual root
            // (i.e. they called it for a control inside a popup.
            if (!ReferenceEquals(_rootVisual, relativeTo.VisualRoot) && relativeTo.VisualRoot is { })
            {
                // Convert to absolute screen coordinates.
                var screenPt = _rootVisual.PointToScreen(pt);

                // Convert to client co-ordinates of the visual inside the other visual root.
                return relativeTo.PointToClient(screenPt);
            }

            return pt * _rootVisual.TransformToVisual(relativeTo) ?? default;
        }

        /// <summary>
        /// Gets the pointer position relative to a control.
        /// </summary>
        /// <param name="relativeTo">The visual whose coordinate system to use. Pass null for toplevel coordinate system</param>
        /// <returns>The pointer position in the control's coordinates.</returns>
        public Point GetPosition(Visual? relativeTo) => GetPosition(_rootVisualPosition, relativeTo);

        /// <summary>
        /// Returns the PointerPoint associated with the current event
        /// </summary>
        /// <param name="relativeTo">The visual whose coordinate system to use. Pass null for toplevel coordinate system</param>
        /// <returns></returns>
        public PointerPoint GetCurrentPoint(Visual? relativeTo)
            => new PointerPoint(Pointer, GetPosition(relativeTo), _properties);

        /// <summary>
        /// Returns the PointerPoint associated with the current event
        /// </summary>
        /// <param name="relativeTo">The visual which coordinate system to use. Pass null for toplevel coordinate system</param>
        /// <returns></returns>
        public IReadOnlyList<PointerPoint> GetIntermediatePoints(Visual? relativeTo)
        {
            var previousPoints = _previousPoints?.Value;            
            if (previousPoints == null || previousPoints.Count == 0)
                return new[] { GetCurrentPoint(relativeTo) };
            var points = new PointerPoint[previousPoints.Count + 1];
            for (var c = 0; c < previousPoints.Count; c++)
            {
                var pt = previousPoints[c];
                var pointProperties = new PointerPointProperties(_properties, pt);
                points[c] = new PointerPoint(Pointer, GetPosition(pt.Position, relativeTo), pointProperties);
            }

            points[points.Length - 1] = GetCurrentPoint(relativeTo);
            return points;
        }

        /// <summary>
        /// Prevents this event from being handled by other gesture recognizers in the route
        /// </summary>
        public void PreventGestureRecognition()
        {
            IsGestureRecognitionSkipped = true;
        }

        /// <summary>
        /// Gets the state the pointer device had when this event occurred.
        /// </summary>
        public PointerPointProperties Properties => _properties;
    }
    
    public enum MouseButton
    {
        None,
        Left,
        Right,
        Middle,
        XButton1,
        XButton2
    }

    public class PointerPressedEventArgs : PointerEventArgs
    {
        [Unstable("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")]
        public PointerPressedEventArgs(
            object source,
            IPointer pointer,
            Visual rootVisual, Point rootVisualPosition,
            ulong timestamp,
            PointerPointProperties properties,
            KeyModifiers modifiers,
            int clickCount = 1)
            : base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition,
                timestamp, properties, modifiers)
        {
            ClickCount = clickCount;
        }

        public int ClickCount { get; }
    }

    public class PointerReleasedEventArgs : PointerEventArgs
    {
        [Unstable("This constructor might be removed in 12.0. For unit testing, consider using IHeadlessWindow mouse methods.")]
        public PointerReleasedEventArgs(
            object source, IPointer pointer,
            Visual rootVisual, Point rootVisualPosition, ulong timestamp,
            PointerPointProperties properties, KeyModifiers modifiers,
            MouseButton initialPressMouseButton)
            : base(InputElement.PointerReleasedEvent, source, pointer, rootVisual, rootVisualPosition,
                timestamp, properties, modifiers)
        {
            InitialPressMouseButton = initialPressMouseButton;
        }

        /// <summary>
        /// Gets the mouse button that triggered the corresponding PointerPressed event
        /// </summary>
        public MouseButton InitialPressMouseButton { get; }
    }

    public class PointerCaptureLostEventArgs : RoutedEventArgs
    {
        public IPointer Pointer { get; }

        [Unstable("This constructor might be removed in 12.0. If you need to remove capture, use stable methods on the IPointer instance.,")]
        public PointerCaptureLostEventArgs(object source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent)
        {
            Pointer = pointer;
            Source = source;
        }
    }
}
